Skip to content

Conversation

ronjalh
Copy link
Member

@ronjalh ronjalh commented Oct 30, 2026 •

Summary

Connects the filters (#127) and the sort (#128) on the Elements page, in one pull request. It replaces #134 (the sort), which is closed: this branch contains the sort from #134 and the filters from #127. The two issues stay separate. Part of #110.

The filters (#127)

  • Apparatus and Element group: one choice each, with an "All ..." item, and the menu closes after the choice. The group menu lists the groups of the chosen apparatus in the order of their number (a vault group is shown as "Group N", like in the list). Choosing another apparatus clears the group.
  • Difficulty: one or more values, in order. For bars, beam and floor the letters A-J (A = 0.1 ... J = 1.0). For vault bands of the D-score (2.0–2.9, 3.0–3.9 ... 6.0–6.9), because there are 23 different D-values. A band is sent as the D-values that exist inside it (from Apparatus.difficultyValues), so only bands that have a vault are offered and the API needs no ranges. The chosen difficulty is dropped when the apparatus changes between vault and the others, because the scales differ.
  • A filter that cannot be used is disabled, with the reason: the group filter before an apparatus is chosen. It uses aria-disabled and not the disabled attribute, so the keyboard can still reach it; the reason is a tooltip (hover, focus, and also on a tap, because a tooltip does not open on touch) and the aria-describedby text.
  • The address: ?search=...&apparatus=UB&group=UB-2&difficulty=A,B, and for vault the bands ?apparatus=VT&difficulty=2,3. A filter change is a new history step, so the back button undoes it. "Clear filters" appears when a filter is set, and moves the focus to the first filter because the button disappears.
  • A bad address (an apparatus or group that does not exist, a group of another apparatus, a difficulty that is not one of the options) is cleaned before it is sent, and an address with a filter waits for the structure, so the API never answers BAD_USER_INPUT. Each change is made on the address as the last change left it, so two quick choices (two bands) both count; found in the browser, because React Router's function form only sees the address of the last render.

The sort (#128)

  • One list of choices that each say the outcome, as sort menus on other sites do: "Code: low to high", "Code: high to low", "Difficulty: low to high", "Difficulty: high to low", "Name: A to Z", "Name: Z to A". It replaces the two settings (a field and "Ascending"/"Descending") that were in feat: connect the sort on the Elements page #134. The button says the choice ("Sort by Difficulty: high to low"). The address stays ?sort=difficulty&dir=desc.
  • Difficulty is only offered with an apparatus. It sorts by the value on bars, beam and floor, and by the D-score on vault. An address that asks for it without an apparatus gives the default sort as a whole (not "Code: high to low").

The backend (small, for the vault filter)

  • filter.difficultyValues now also works for vault (apparatusId: "VT", or a vault group): it compares the D-value, 0.1-10.0 in steps of 0.1. Without an apparatus or a vault group it still only filters elements, because the two scales cannot be compared. It used to answer BAD_USER_INPUT for vault.
  • New field Apparatus.difficultyValues: the values that exist on the apparatus, lowest first. It is one groupBy through Prisma (no raw SQL), read once and kept for ten minutes together with the apparatus and the groups, so it costs nothing per request.
  • schema.graphql, docs/API.md and the tests are updated. No new dependency, no migration.

Page titles and documentation

  • Every page has a title that says what it is (WCAG 2.4.2): "Elements – WAG CoP Planner", "Element 6.210 – Uneven bars – WAG CoP Planner", "Your gymnasts", "Calculator", "About", "Page not found", and a title for the loading, not-found and error states of the element page. It is a small PageTitle component with the React 19 <title> (moved to the head by React, no library), and the default in index.html is "WAG CoP Planner". Before, every page was called "t29-project-2".
  • README: the outdated status ("the frontend is still the Vite starter page") is corrected, and there is a new section "The Elements page: search, filters and sort" with the choices and the reasons, the accessibility work, the tests and the known limits. docs/AI_USAGE.md describes the whole pull request.

Choices

  • Ready-made parts only: the Base UI Menu and Tooltip that the project already depends on, and useSearchParams from React Router.
  • Scales with the catalog. The options are the 4 apparatus with their groups and difficulty values (one small query, kept by the Apollo cache and in memory by the server). Filtering, counting, sorting and paging run on the server with cursors, so the page only holds the pages that are shown.
  • Runs on the virtual machine as before: PostgreSQL directly, no Docker; VITE_API_URL=/project2/graphql npm run build gives /project2/ paths.

Testing

  • tsc -b, eslint, npm run build (also with VITE_API_URL=/project2/graphql), the backend tsc and 442 tests pass: the frontend, and the backend search and server tests against a seeded database.
  • New tests: each filter, several values, vault D-values, the address in both directions, the disabled filter and its reason, clear and where the focus goes, the sort list, the search, the filters and the sort together, and on the backend the vault filter, the range check and Apparatus.difficultyValues.
  • Mutation check: I broke each behavior one at a time and checked that a test fails. This found a dead onClick handler (removed), a menu that stayed open after one choice, and a test that was too weak (all fixed).
  • In a real browser (Edge, driven by Playwright, on localhost) against the real backend and the real catalog (562 entries): 26 checks, including the filters, the group order, the tooltip on hover, keyboard focus and tap, the address, reload, back, clear, the sort list, vault D-value filter and sort by the D-score in both directions, search with a filter and "Load more", a wrong address, and the title of every page after a client-side navigation. No console errors and no failed requests.

Open points

  • The backend has to be updated on the virtual machine together with the frontend, because the page asks for Apparatus.difficultyValues.
  • The page has not been tested with a screen reader (NVDA or VoiceOver); that has to be done by a person.
  • Merge this and delete the branch; feat: connect the sort on the Elements page #134 is already closed.

Closes #127
Closes #128

🤖 Generated with Claude Code

@ronjalh ronjalh requested a review from a team as a code owner October 30, 2026 16:14
@ronjalh ronjalh self-assigned this Oct 30, 2026
@ronjalh ronjalh changed the title feat: connect the filters on the Elements page feat: connect the filters and the sort on the Elements page Oct 30, 2026
Copy link
Member

@xeniag xeniag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, one quick change before merge:

if loading the filter structure fails, the page can discard the URL filters and show unfiltered results. It should show an error and skip the search instead (ElementsPage.tsx).

Copy link
Member

@xeniag xeniag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good!

@xeniag xeniag merged commit 22435b4 into dev Oct 30, 2026
@ronjalh ronjalh mentioned this pull request Nov 5, 2026
13 tasks
Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants